package com.example.tys.baseapplication.NetModel.executor.rxandroid;

import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;

import com.example.tys.baseapplication.NetModel.executor.CallBack;
import com.example.tys.baseapplication.NetModel.network.ClientException;
import com.example.tys.baseapplication.util.JsonUtil;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import okhttp3.MediaType;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * @author tys
 * @date 2017/10/1
 */

public abstract class RxAndroidExe<Result> {

    private static final String TAG = "RxAndroidExe";

    /**
     * 直接实现{@link CallBack}接口的类
     */
    private final CallBack<Result> callback;

    private static final String MEDIA_TYPE_APPLICATION = "application";

    private static final String MEDIA_SUB_TYPE_JSON = "json";


    /**
     * @param callback 直接实现{@link CallBack}接口的类
     */
    public RxAndroidExe(CallBack<Result> callback) {
        this.callback = callback;
    }


    public void execute() {
        Observable.fromCallable(() -> {
            Response response = doTask();
            return convert(response);
        }).subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(callback::onSuccess, callback::onFailed);
    }

    @Nullable
    private Result convert(okhttp3.Response response) throws ClientException, IOException {
        Result result;
        if (response == null) {
            return null;
        }

        Class<Result> targetType = getResultType();
        if (targetType == null) {
            Log.e(TAG, "result type is null");
            return null;
        }

        if (okhttp3.Response.class.isAssignableFrom(targetType)) {
            return (Result) response;
        }

        if (response.isSuccessful()) {
            ResponseBody body = response.body();
            if (body == null) {
                return null;
            }
            MediaType mediaType = body.contentType();
            if (mediaType != null) {
                String type = mediaType.type();
                String subtype = mediaType.subtype();
                if (MEDIA_TYPE_APPLICATION.equals(type)) {
                    switch (subtype) {
                        case MEDIA_SUB_TYPE_JSON:
                            result = convertJson(body.string(), targetType);
                            break;
                        default:
                            throw new ClientException("not support media type "+ mediaType.toString());
                    }
                } else {
                    throw new ClientException("not support media type "+ mediaType.toString());
                }
            } else {
                throw new ClientException("mediaType is null");
            }
        } else {
            throw new ClientException(response.code(), response.message());
        }
        return result;
    }

    private Result convertJson(String string, Class<Result> targetType) {
        return JsonUtil.toObject(string, targetType);
    }

    protected abstract okhttp3.Response doTask() throws ClientException, IOException;


    /**
     * 获取{@link CallBack}泛型类型。</p>
     *
     * @return 泛型类型 e. String
     */
    @Nullable
    private Class<Result> getResultType() {
        Type ret = null;
        for (Type type : callback.getClass().getGenericInterfaces()) {
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type rawType = parameterizedType.getRawType();
                if (rawType instanceof Class) {
                    if (isCorrectClass((Class<?>) rawType)) {
                        Type[] arguments = parameterizedType.getActualTypeArguments();
                        if (arguments != null && arguments.length > 0) {
                            ret = arguments[0];
                        }
                        break;
                    }
                }
            }
        }
        return (Class<Result>) ret;
    }

    private boolean isCorrectClass(Class<?> rawType) {
        return CallBack.class.isAssignableFrom(rawType);
    }
}
